
import { _decorator, Component, Node, Vec3, Color, __private, MeshRenderData } from 'cc';
const { ccclass, property } = _decorator;

/**
 * Predefined variables
 * Name = G3DImpl
 * DateTime = Mon Jan 10 2022 10:50:07 GMT+0800 (中国标准时间)
 * Author = 雨到了1分享
 * FileBasename = G3DImpl.ts
 * FileBasenameNoExtension = G3DImpl
 * URL = db://assets/scripts/app/mediator/common/G3DImpl.ts
 * ManualUrl = https://docs.cocos.com/creator/3.4/manual/zh/
 *
 */
 
export class Point3 extends Vec3 {
    public dx = 0;
    public dy = 0;
    public dz = 0;
    public dmx = 0;
    public dmy = 0;
    public dmz = 0;
    public flags = 0;
    public len = 0;
    public lineLength = 0;
    constructor(x:number,y:number,z:number){
        super(x,y,z);
        this.reset();
    }
    public reset(){
        this.dx = 0;
        this.dy = 0;
        this.dz = 0;
        this.dmx = 0;
        this.dmy = 0;
        this.dmz = 0;
        this.flags = 0;
        this.len = 0;
        this.lineLength = 0;
    }
}

export class Path3 {
    public closed = false;
    public bevel = 0;
    public complex = true;
    public points:Point3[] = [];
    constructor (){
        this.reset();
    }
    public reset(){
        this.closed = false;
        this.bevel = 0;
        this.complex = true;
        if (this.points ){
            this.points.length = 0;
        }else{
            this.points = [];
        }
    }
}

export class G3DImpl  {
    public dataOffset = 0;
    public updatePathOffset = false;

    public pathLength = 0;
    public pathOffset = 0;

    public paths: Path3[] = [];
    // inner properties
    public tessTol = 0.25;
    public distTol = 0.01;
    public fillColor = Color.WHITE.clone();
    public lineCap = LineCap.BUTT;
    public strokeColor = Color.BLACK.clone();
    public lineJoin = LineJoin.MITER;
    public lineWidth = 0;

    public pointsOffset = 0;

    private _commandX = 0;
    private _commandY = 0;
    private _points: Point3[] = [];
    private _renderDataList: MeshRenderData[] = [];
    private _curPath: Path3 | null = null;

    

    public moveTo (x: number, y: number,z:number) {
        if (this.updatePathOffset) {
            this.pathOffset = this.pathLength;
            this.updatePathOffset = false;
        }

        this._addPath();
        this.addPoint(x, y,z, PointFlags.PT_CORNER);

        this._commandX = x;
        this._commandY = y;
    }

    public lineTo (x: number, y: number,z:number) {
        this.addPoint(x, y,z, PointFlags.PT_CORNER);

        this._commandX = x;
        this._commandY = y;
    }

    public bezierCurveTo (c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number) {
        const path = this._curPath!;
        const last = path.points[path.points.length - 1];
        if (!last) {
            return;
        }

        if (last.x === c1x && last.y === c1y && c2x === x && c2y === y) {
            this.lineTo(x, y,0);
            return;
        }

        tesselateBezier(this, last.x, last.y, c1x, c1y, c2x, c2y, x, y, 0, PointFlags.PT_CORNER);

        this._commandX = x;
        this._commandY = y;
    }

    public quadraticCurveTo (cx: number, cy: number, x: number, y: number) {
        const x0 = this._commandX;
        const y0 = this._commandY;
        this.bezierCurveTo(x0 + 2.0 / 3.0 * (cx - x0), y0 + 2.0 / 3.0 * (cy - y0), x + 2.0 / 3.0 * (cx - x), y + 2.0 / 3.0 * (cy - y), x, y);
    }

    public arc (cx: number, cy: number, r: number, startAngle: number, endAngle: number, counterclockwise: boolean) {
        arc(this, cx, cy, r, startAngle, endAngle, counterclockwise);
    }

    public ellipse (cx: number, cy: number, rx: number, ry: number) {
        ellipse(this, cx, cy, rx, ry);
        this._curPath!.complex = false;
    }

    public circle (cx: number, cy: number, r: number) {
        ellipse(this, cx, cy, r, r);
        this._curPath!.complex = false;
    }

    public rect (x: number, y: number, w: number, h: number) {
        this.moveTo(x, y,0);
        this.lineTo(x + w, y,0);
        this.lineTo(x + w, y + h,0);
        this.lineTo(x, y + h,0);

        this.close();
        this._curPath!.complex = false;
    }

    public roundRect (x: number, y: number, w: number, h: number, r: number) {
        roundRect(this, x, y, w, h, r);
        this._curPath!.complex = false;
    }

    public clear () {
        // this.pathLength = 0;
        // this.pathOffset = 0;
        // this.pointsOffset = 0;
        // this.dataOffset = 0;
        // this._curPath = null;
        // this.paths.length = 0;
        // this._points.length = 0;

        // const dataList = this._renderDataList;
        // for (let i = 0, l = dataList.length; i < l; i++) {
        //     const data = dataList[i];
        //     if (!data) {
        //         continue;
        //     }

        //     MeshRenderData.remove(data);
        // }

        // this._renderDataList.length = 0;
    }

    public close () {
        this._curPath!.closed = true;
    }
 
    public requestRenderData ():MeshRenderData {
        // const renderData = MeshRenderData.add();
        // this._renderDataList.push(renderData);

        // return renderData;
        return null;
    }

    public getRenderDataList () {
        if (this._renderDataList.length === 0) {
            this.requestRenderData();
        }

        return this._renderDataList;
    }

    public addPoint (x: number, y: number,z:number, flags: PointFlags) {
        const path = this._curPath;
        if (!path) {
            return;
        }

        const points = this._points;
        const pathPoints = path.points;

        const offset = this.pointsOffset++;
        let pt: Point3 = points[offset];

        if (!pt) {
            pt = new Point3(x, y,z);
            points.push(pt);
        } else {
            pt.x = x;
            pt.y = y;
        }

        pt.flags = flags;
        pathPoints.push(pt);
    }

    private _addPath () {
        const offset = this.pathLength;
        let path = this.paths[offset];

        if (!path) {
            path = new Path3();

            this.paths.push(path);
        } else {
            path.reset();
        }

        this.pathLength++;
        this._curPath = path;

        return path;
    }
}



const PI = Math.PI;
const min = Math.min;
const max = Math.max;
const cos = Math.cos;
const sin = Math.sin;
const abs = Math.abs;
const sign = Math.sign;

const KAPPA90 = 0.5522847493;

export function arc (ctx: G3DImpl, cx: number, cy: number, r: number, startAngle: number, endAngle: number, counterclockwise: boolean) {
    counterclockwise = counterclockwise || false;

    let a = 0;
    let da = 0;
    let hda = 0;
    let kappa = 0;
    let dx = 0;
    let dy = 0;
    let x = 0;
    let y = 0;
    let tanx = 0;
    let tany = 0;
    let px = 0;
    let py = 0;
    let ptanx = 0;
    let ptany = 0;
    let i = 0;
    let ndivs = 0;

    // Clamp angles
    da = endAngle - startAngle;
    if (counterclockwise) {
        if (abs(da) >= PI * 2) {
            da = PI * 2;
        } else {
            while (da < 0) { da += PI * 2; }
        }
    } else if (abs(da) >= PI * 2) {
        da = -PI * 2;
    } else {
        while (da > 0) { da -= PI * 2; }
    }

    // Split arc into max 90 degree segments.
    ndivs = max(1, min(abs(da) / (PI * 0.5) + 0.5, 5)) | 0;
    hda = da / ndivs / 2.0;
    kappa = abs(4.0 / 3.0 * (1 - cos(hda)) / sin(hda));

    if (!counterclockwise) { kappa = -kappa; }

    for (i = 0; i <= ndivs; i++) {
        a = startAngle + da * (i / ndivs);
        dx = cos(a);
        dy = sin(a);
        x = cx + dx * r;
        y = cy + dy * r;
        tanx = -dy * r * kappa;
        tany = dx * r * kappa;

        if (i === 0) {
            ctx.moveTo(x, y,0);
        } else {
            ctx.bezierCurveTo(px + ptanx, py + ptany, x - tanx, y - tany, x, y);
        }
        px = x;
        py = y;
        ptanx = tanx;
        ptany = tany;
    }
}

export function ellipse (ctx: G3DImpl, cx: number, cy: number, rx: number, ry: number) {
    ctx.moveTo(cx - rx, cy,0);
    ctx.bezierCurveTo(cx - rx, cy + ry * KAPPA90, cx - rx * KAPPA90, cy + ry, cx, cy + ry);
    ctx.bezierCurveTo(cx + rx * KAPPA90, cy + ry, cx + rx, cy + ry * KAPPA90, cx + rx, cy);
    ctx.bezierCurveTo(cx + rx, cy - ry * KAPPA90, cx + rx * KAPPA90, cy - ry, cx, cy - ry);
    ctx.bezierCurveTo(cx - rx * KAPPA90, cy - ry, cx - rx, cy - ry * KAPPA90, cx - rx, cy);
    ctx.close();
}

export function roundRect (ctx: G3DImpl, x: number, y: number, w: number, h: number, r: number) {
    if (r < 0.1) {
        ctx.rect(x, y, w, h);
    } else {
        const rx = min(r, abs(w) * 0.5) * sign(w);
        const ry = min(r, abs(h) * 0.5) * sign(h);

        ctx.moveTo(x, y + ry,0);
        ctx.lineTo(x, y + h - ry,0);
        ctx.bezierCurveTo(x, y + h - ry * (1 - KAPPA90), x + rx * (1 - KAPPA90), y + h, x + rx, y + h);
        ctx.lineTo(x + w - rx, y + h,0);
        ctx.bezierCurveTo(x + w - rx * (1 - KAPPA90), y + h, x + w, y + h - ry * (1 - KAPPA90), x + w, y + h - ry);
        ctx.lineTo(x + w, y + ry,0);
        ctx.bezierCurveTo(x + w, y + ry * (1 - KAPPA90), x + w - rx * (1 - KAPPA90), y, x + w - rx, y);
        ctx.lineTo(x + rx, y,0);
        ctx.bezierCurveTo(x + rx * (1 - KAPPA90), y, x, y + ry * (1 - KAPPA90), x, y + ry);
        ctx.close();
    }
}

export function tesselateBezier (
    ctx: G3DImpl, x1: number, y1: number,
    x2: number, y2: number,
    x3: number, y3: number,
    x4: number, y4: number,
    level: number, type: number,
) {
    let x12 = 0;
    let y12 = 0;
    let x23 = 0;
    let y23 = 0;
    let x34 = 0;
    let y34 = 0;
    let x123 = 0;
    let y123 = 0;
    let x234 = 0;
    let y234 = 0;
    let x1234 = 0;
    let y1234 = 0;
    let dx = 0;
    let dy = 0;
    let d2 = 0;
    let d3 = 0;

    if (level > 10) {
        return;
    }

    x12 = (x1 + x2) * 0.5;
    y12 = (y1 + y2) * 0.5;
    x23 = (x2 + x3) * 0.5;
    y23 = (y2 + y3) * 0.5;
    x34 = (x3 + x4) * 0.5;
    y34 = (y3 + y4) * 0.5;
    x123 = (x12 + x23) * 0.5;
    y123 = (y12 + y23) * 0.5;

    dx = x4 - x1;
    dy = y4 - y1;
    d2 = abs((x2 - x4) * dy - (y2 - y4) * dx);
    d3 = abs((x3 - x4) * dy - (y3 - y4) * dx);

    if ((d2 + d3) * (d2 + d3) < ctx.tessTol * (dx * dx + dy * dy)) {
        ctx.addPoint(x4, y4,0, type === 0 ? type | PointFlags.PT_BEVEL : type);
        return;
    }

    x234 = (x23 + x34) * 0.5;
    y234 = (y23 + y34) * 0.5;
    x1234 = (x123 + x234) * 0.5;
    y1234 = (y123 + y234) * 0.5;

    tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1, 0);
    tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1, type);
}



/**
 * @en Enum for LineCap.
 * @zh 线段末端属性
 * @enum Graphics.LineCap
 */
 export enum LineCap {
    /**
     * @en The ends of lines are squared off at the endpoints.
     * @zh 线段末端以方形结束。
     */
    BUTT = 0,

    /**
     * @en The ends of lines are rounded.
     * @zh 线段末端以圆形结束。
     */
    ROUND = 1,

    /**
     * @en The ends of lines are squared off by adding a box with an equal width and half the height of the line's thickness.
     * @zh 线段末端以方形结束，但是增加了一个宽度和线段相同，高度是线段厚度一半的矩形区域。
     */
    SQUARE = 2,
}


/**
 * @en Enum for LineJoin.
 * @zh 线段拐角属性
 * @enum Graphics.LineJoin
 */
export enum LineJoin {
    /**
     * @en Fills an additional triangular area between the common endpoint of connected segments, and the separate outside rectangular corners of each segment.
     * @zh 在相连部分的末端填充一个额外的以三角形为底的区域， 每个部分都有各自独立的矩形拐角。
     */
    BEVEL = 0,

    /**
     * @en Rounds off the corners of a shape by filling an additional sector of disc centered at the common endpoint of connected segments.
     * The radius for these rounded corners is equal to the line width.
     * @zh 通过填充一个额外的，圆心在相连部分末端的扇形，绘制拐角的形状。 圆角的半径是线段的宽度。
     */
    ROUND = 1,

    /**
     * @en Connected segments are joined by extending their outside edges to connect at a single point,
     * with the effect of filling an additional lozenge-shaped area.
     * @zh 通过延伸相连部分的外边缘，使其相交于一点，形成一个额外的菱形区域。
     */
    MITER = 2,
}


// PointFlags
export enum PointFlags {
    PT_CORNER = 0x01,
    PT_LEFT = 0x02,
    PT_BEVEL = 0x04,
    PT_INNERBEVEL = 0x08,
}

